/* vcc/cmd.c 
 * 
 * This file is part of vcc. 
 * 
 * vcc is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * vcc is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with vcc. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <signal.h>
#include <getopt.h>

#include <klist.h>
#include <vcc/vcc.h>
#include <vcc/plugin.h>
#include <vcc/version.h>
#include <vcc/pretty.h>
#include <vcc/interfaces.h>


int do_cmd_help(int, char **);
int do_cmd_quit(int, char **);
int do_cmd_ls(int, char **);
int do_cmd_newse(int, char **);
int do_cmd_currs(int, char **);
int do_cmd_swtch(int, char **);
int do_cmd_uinfo(int, char **);
int do_cmd_lself(int, char **);
int do_cmd_incr(int, char **);
int do_cmd_cqd(int, char **);
int do_cmd_ban(int, char **);
int do_cmd_unban(int, char **);
int do_cmd_encry(int, char **);
int do_cmd_pls(int, char **);
int do_cmd_pins(int, char **);
int do_cmd_prem(int, char **);
int do_cmd_ml(int, char **);
int do_cmd_rl(int, char **);
int do_cmd_sys(int, char **);
int do_cmd_sename(int, char **);
int do_cmd_sid(int, char **);
int do_cmd_join(int, char **);

int did_warn = 0;

struct cmdtable cmdtab[] = {
	{ "-help", do_cmd_help, "Show information about every message. " }, 
	{ "-quit", do_cmd_quit, "Disconnect to server and exit vcc. " }, 
	{ "-ls", do_cmd_ls, "List all users online or sessions created. " }, 
	{ "-newse", do_cmd_newse, "Create a new session. " }, 
	{ "-currs", do_cmd_currs, "Get current session id. " }, 
	{ "-swtch", do_cmd_swtch, "Switch to another session. " }, 
	{ "-uinfo", do_cmd_uinfo, "Get one's information. " }, 
	{ "-lself", do_cmd_lself, "Reload information of myself. " }, 
	{ "-incr", do_cmd_incr, "Increase one's score (root required). " }, 
	{ "-ban", do_cmd_ban, "Don't receive more messages from somebody. " }, 
	{ "-unban", do_cmd_unban, "Receive more messages from somebody. " },
	{ "-encry", do_cmd_encry, "Send an encrypted message. " }, 
	{ "-pls", do_cmd_pls, "List plugins installed (not inserted). " }, 
	{ "-pins", do_cmd_pins, "Insert a plugin. " }, 
	{ "-prem", do_cmd_prem, "Remove a plugin. " }, 
	{ "-ml", do_cmd_ml, "Multi-line. " }, 
	{ "-rl", do_cmd_rl, "Send a relay message. " }, 
	{ "-sys", do_cmd_sys, "Get system info. " }, 
	{ "-sname", do_cmd_sename, "Get session name. " }, 
	{ "-sid", do_cmd_sid, "Get session id of name. " }, 
	{ "-join", do_cmd_join, "Join a session (by session-name). " }, 

	{ NULL, NULL, NULL }
};

#define clear_line() 	fprintf(stderr, "\033[2K\r")
#define __unused 	__attribute__((unused))

typedef int (* fptr_t) (int, char **);

fptr_t execute_in(struct cmdtable *tab, char *s, int len) {
	struct cmdtable *t;

	for (t = tab; t->cmd; t++) 
		if (!strncmp(t->cmd, s, len)) 
			return t->do_cmd;

	return NULL;
}


static char *gnw;
#define skip_spaces(p) 	while (isspace(*(p))) (p)++;

static char *getnextword(char *s) {
	char *p;

	if (s) 
		gnw = s;

	skip_spaces(gnw);

	if (!*gnw) 
		return NULL;

	for (p = gnw; *p && *p != '\n' && !isspace(*p); p++) 
		;

	*p = 0;
	s = gnw;
	gnw = ++p;

	return s;
}

/* getopt doesn't work, sh!t */
char *cmd_options;
int cmd_noptions;
static int get_options(int argc, char **argv) {
	char *p;
	int i;

	cmd_noptions = 0;

	for (i = 1; i < argc; i++) {
		if (*(p = argv[i]) != '-') 
			continue;

		if (p[1] == '-') 
			fprintf(stderr, _("warning: long options are not supported. \n"));

		cmd_options[(int) p[1]] = p[1];
		cmd_noptions++;
	}

	return 0;
}


static int get_argv(char *s, char **argv) {
	int i = 0;

	argv[0] = getnextword(s);
	do {
		if (i > 32) 
			argv = realloc(argv, i * sizeof(char *));
	} while ((argv[++i] = getnextword(NULL)));

	return i;
}


int do_cmd(char *s) {
	struct plugin 		*plg;
	char 			*p, 
				**argv;
	int 			len, i, argc;
	fptr_t 			f;

	if (unlikely(!(cmd_options = malloc(256)))) {
		fprintf(stderr, "*** malloc() failed\n");

		return 1;
	}

	memset(cmd_options, 0, 256);

	p = s;

	for (len = 0; *p && !isspace(*p); len++) 
		p++;

	if (unlikely(!(argv = malloc(32 * sizeof(char *))))) {
		fprintf(stderr, "*** malloc() failed\n");

		return 1;
	}

	memset(argv, 0, 32 * sizeof(char *));
	argc = get_argv(s, argv);
	get_options(argc, argv);

	if ((f = execute_in(cmdtab, s, len))) 
		return f(argc, argv);

	for (i = 0; (plg = plugins[i]); i++) 
		if ((f = execute_in(plg->cmdtab, s, len))) 
			return f(argc, argv);

	pretty_unknown_cmd(s);

	return 1;
}


static int __help(struct cmdtable *cmdtab) {
	struct cmdtable *t;

	for (t = cmdtab; t->cmd; t++) 
		pretty_cmd_help(t->cmd, t->help);

	return 0;
}


struct cmdtable *find_in(struct cmdtable *table, char *s) {
	struct cmdtable *cmd;

	for (cmd = table; cmd->cmd; cmd++) 
		if (!(strcmp(cmd->cmd, s))) 
			return cmd;

	return NULL;
}

int do_cmd_help(int argc, char **argv) {
	struct plugin *p;
	struct cmdtable *cmd;
	int i;

	switch (argc) {
	case 1:
		__help(cmdtab);

		for (i = 0; (p = plugins[i]); i++) 
			__help(p->cmdtab);

		break;

	case 2:
		if (!(cmd = find_in(cmdtab, argv[1]))) {
			for (i = 0; (p = plugins[i]); i++) 
				if ((cmd = find_in(p->cmdtab, argv[1]))) 
					break;

			if (!cmd) {
				fprintf(stderr, _("%s: command not found. \n"), argv[1]);
				fprintf(stderr, _("Do you mean -%s? \n"), argv[1]);

				return 1;
			}
		}

		pretty_cmd_help(cmd->cmd, cmd->help);

		break;

	default:
		fprintf(stderr, _("usage: -help [command]\n"));

		break;
	}

	return 0;
}


int do_cmd_quit(int argc, char **argv) {
	(void) argv;

	if (argc != 1) {
		fprintf(stderr, _("usage: -quit\n"));

		return 1;
	}

	printf(_("bye. \n"));
	exit(0);

	return 0;
}


int do_cmd_ls(int argc, char **argv) {
	int type;

	(void) argv;

	if (argc > 2) 
		goto bad_usage;

	if (cmd_options['u']) 
		type = 'u', cmd_noptions--;
	else if (cmd_options['s']) 
		type = 's', cmd_noptions--;
	if (cmd_noptions) 
		goto bad_usage;

	switch (type) {
	case 'u':
		get_users();
		break;

	case 's':
		get_sessions();
		break;
	}

	return 0;

bad_usage:
	fprintf(stderr, _("usage: -ls [-us]\n"));

	return 1;
}


int do_cmd_ls_bh(struct vcc_relay_header *req) {
	int n, i;
	char *msg;

	clear_line();
	n = ntohl(req->uid);
	msg = relay_get_msg(fd, req);

	for (i = 0; i < n; i++) 
		printf("%s\n", msg + i * USRNAME_SIZE);

	pretty_prompt(usrname);
	free(msg);

	return 0;
}


int do_cmd_lsse_bh(struct vcc_request *req) {
	int n, i;
	char *p;

	n = ntohl(req->uid);

	clear_line();

	if (unlikely(ntohl(req->reqtype) != REQ_CTL_SESS)) {
		fprintf(stderr, "--- do_cmd_ls_bh: bad package. \n");

		return 1;
	}

	for (i = 0; i < n; i++) {
		p = req->msg + i * USRNAME_SIZE;

		printf("%d: \t%s\n", get_name_sid(p), p);
	}

	pretty_prompt(usrname);

	return 0;
}


int do_cmd_newse(int argc, char **argv) {
	char sname[USRNAME_SIZE];

	switch (argc) {
	case 1:
		fprintf(stderr, _("Name of new session: "));
		scanf("%s", sname);

		vcc_create_session(sname);

		break;
	case 2:
		vcc_create_session(argv[1]);
		break;

	default:
		fprintf(stderr, _("usage: -newse [session-name]\n"));
		return 1;
	}

	printf(_("Session create request sent. \n"));

	return 0;
}

int do_cmd_currs(int argc, char **argv) {
	(void) argv;

	if (argc != 1) {
		fprintf(stderr, _("usage: -currs\n"));

		return 1;
	}

	printf(_("Current session id: %d\n"), current_sid);

	return 0;
}


int do_cmd_swtch(int argc, char **argv) {
	int sid;

	switch (argc) {
	case 1:
		printf(_("Old session id: %d\n"), current_sid);
		fprintf(stderr, _("New session id: "));
		scanf("%d", &sid);

		break;
	case 2:
		sscanf(argv[1], "%d", &sid);
		break;

	default:
		fprintf(stderr, _("usage: -swtch [session-number]\n"));
		return 1;
	}

	current_sid = sid;
	join_session(current_sid);

	return 0;
}

int do_cmd_quits(int argc, char **argv) {
	int sid;

	switch (argc) {
	case 1:
		printf("sid: ");
		scanf("%d", &sid);

		break;
	case 2:
		sscanf(argv[1], "%d", &sid);
		break;

	default:
		fprintf(stderr, _("usage: -quits [session-number]\n"));
		return 1;
	}

	current_sid = sid;
	quit_session(current_sid);

	return 0;
}


int lself = 0;

int do_cmd_uinfo(int argc, char **argv) {
	char name[USRNAME_SIZE];

	if (argc > 2) {
		fprintf(stderr, _("usage: -uinfo [user-name]\n"));

		return 1;
	}

	if (!lself) {
		if (argc == 1) {
			fprintf(stderr, _("username: "));
			scanf("%s", name);

			get_user_info(name);
		}

		else 
			get_user_info(argv[1]);
	}

	else 
		get_user_info(usrname);

	return 0;
}


struct user {
	int 	score;
	int 	level;

	char 	usrname[USRNAME_SIZE];

	/* it's blank */
	char 	passwd[PASSWD_SIZE];
};


int do_cmd_uinfo_bh(struct vcc_request *req) {
	struct user *u = (struct user *) req->msg;
	
	clear_line();

	if (ntohl(req->uid) == -1u) {
		printf(_("user not found. \n"));
		pretty_prompt(usrname);

		return 1;
	}

	if (lself) {
		lself = 0;
		self_level = ntohl(u->level);

		printf(_("Level of yourself: %d\n"), self_level);
	}

	else {
		printf(_("%s's info: \n"), u->usrname);
		printf(_("\tscore\tlevel\n"));
		printf("\t%5d\t%d\n", ntohl(u->score), ntohl(u->level));
	}

	pretty_prompt(usrname);
	
	return 0;
}

int do_cmd_lself(int argc, char **argv) {
	(void) argv;

	if (argc != 1) {
		fprintf(stderr, _("usage: -lself\n"));

		return 1;
	}

	get_user_info(usrname);

	return 0;
}


int do_cmd_incr(int argc, char **argv) {
	char name[USRNAME_SIZE];
	int incr;

	switch (argc) {
	case 1:
		fprintf(stderr, _("username: "));
		scanf("%s", name);

		fprintf(stderr, _("increment: "));
		scanf("%d", &incr);

		incr_score(name, incr);

		break;

	case 3:
		if (!sscanf(argv[2], "%d", &incr)) {
			fprintf(stderr, _("argv[2] should be a number. \n"));

			return 1;
		}

		incr_score(argv[1], incr);
	
		break;

	default:
		fprintf(stderr, _("usage: -incr [usrname] && [increment]\n"));

		return 1;
	}

	return 0;
}


int do_cmd_incr_bh(struct vcc_request *req) {
	clear_line();

	if (!ntohl(req->uid)) 
		printf(_("Sucessfully. \n"));

	else 
		printf("*** failed. \n");

	pretty_prompt(usrname);

	return 0;
}

int do_cmd_ban(int argc, char **argv) {
	char 			*people_name, people_arr[USRNAME_SIZE];
	struct banlist 		*i;
	struct klist_node 	*n;

	
	switch (argc) {
	case 1:
		fprintf(stderr, _("Enter the user to ban: "));
		scanf("%s", people_arr);
		people_name = strdup(people_arr);

		break;

	case 2:
		people_name = strdup(argv[1]);

		break;

	default:
		fprintf(stderr, _("usage: -ban [user-name]\n"));

		return 1;
	}

	for (n = banned_people.next; n != &banned_people; n = n->next) {
		i = banlist_of(n);

		if (!strcmp(people_name, i->usrname)) {
			fprintf(stderr, _("User %s is already banned\n"), people_name);
			free(people_name);

			return 0;
		}
	}

	if (unlikely(!(i = malloc(sizeof(struct banlist))))) {
		fprintf(stderr, "*** malloc() failed\n");

		return 1;
	}

	i->usrname = people_name;
	klist_init(&i->node);

	klist_add(&banned_people, &i->node);

	return 0;
}

int do_cmd_unban(int argc, char **argv) {
	char 			*people_name;
	struct klist_node 	*i;
	struct banlist 		*ban;

	switch (argc) {
	case 1:
		people_name = malloc(USRNAME_SIZE + 1);

		fprintf(stderr, _("Enter the user to unban: "));
		scanf("%s", people_name);

		break;

	case 2:
		people_name = argv[1];

		break;

	default:
		fprintf(stderr, _("usage: -unban [user-name]\n"));

		return 1;
	}
	
	for (i = banned_people.next; i != &banned_people; i = i->next) {
		ban = banlist_of(i);

		if (!strcmp(people_name, ban->usrname)) {
			klist_del(&banned_people, i);

			free(ban->usrname);

			break;
		}
	}

	if (i == &banned_people) 
		fprintf(stderr, _("User not banned. \n"));

	if (argc == 1) 
		free(people_name);

	return 0;
}


static char const warn_msg[] = {
	"warning: encrypting messages are not always usable. in usual, that man which \n"
	"talks with you gets something f**king, in the starting, or even all the message \n" 
	"is some f**king guys. \n"
	"do you want to continue? (y/N) "
};


static int show_warn_msg(void) {
	char c;

	fprintf(stderr, "%s", _(warn_msg));

	for (;;) {
		read(0, &c, 1);

		if (c == 'y' || c == 'Y') 
			return 0;

		else if (c == 'n' || c == 'N') 
			return 1;

		printf(_("Please answer y / n\n"));
	}

	return 1;
}


int do_cmd_encry(int argc, char **argv) {
	char msg[MSG_SIZE];
	char *p;

	if (!did_warn) {
		did_warn = 1;

		if (show_warn_msg()) 
			return 0;
	}

	switch (argc) {
	case 1:
		memset(msg, 0, MSG_SIZE);

		fprintf(stderr, _("Message: "));
		scanf("%s", msg);
		p = msg;

		break;
	case 2:
		p = argv[1];
		break;

	default:
		fprintf(stderr, _("usage: -encry [message]\n"));

		return 1;
	}

	encrypt(p);
	send_msg_encrypted(p, usrname);
	pretty_new_msg(usrname, p, current_sid, MSG_ENCRYPTED);

	return 0;
}

int do_cmd_ml(int argc, char **argv) {
	char 	*p;
	int 	i, size;

	(void) 	argv;

	if (argc != 1) {
		fprintf(stderr, _("usage: -ml\n"));

		return 1;
	}

	if (unlikely(!(p = malloc(MSG_SIZE)))) {
		fprintf(stderr, "*** malloc() failed\n");

		return 1;
	}

	printf(_("Type 'finish' at the starting of a line to finish. \n"));

	for (i = 0; i < (int) MSG_SIZE; i += size) {
		size = read(0, p + i, 128);

		if (!strncmp(p + i, "finish", 6)) 
			break;
	}

	p[i] = 0;
	handle_command(p);
	free(p);

	return 0;
}

int do_cmd_rl(int argc, char **argv) {
	char 	*msg, *broadcast;
	int 	i;

	if (argc == 1) {
		fprintf(stderr, _("usage: -rl user-to-visible or `-' (broadcast) message\n"));

		return 1;
	}

	if (unlikely(!(msg = malloc(PATH_MAX)))) {
		fprintf(stderr, "*** malloc() failed\n");

		return 1;
	}

	memset(msg, 0, PATH_MAX);

	for (i = 2; i < argc; i++) {
		strcat(msg, argv[i]);

		/* well, we can't know the real space, but using a space is always right */
		strcat(msg, " ");
	}

	broadcast = !strcmp(argv[1], "-") ? NULL : argv[1];

	send_relay_msg(msg, usrname, broadcast);
	pretty_new_msg(usrname, msg, current_sid, MSG_NEW_RELAY | 
			(broadcast ? MSG_NEW_ONLY_VISIBLE : 0));

	free(msg);

	return 0;
}


int do_cmd_sys(int argc, char **argv) {
	(void) argc;
	(void) argv;

	return get_sys_info();
}

int do_cmd_sename(int argc, char **argv) {
	int sid;
	char buf[USRNAME_SIZE];

	/* it invalidates the buffer */

	memset(buf, 0, USRNAME_SIZE);

	switch (argc) {
	case 1:
		fprintf(stderr, "sid: ");
		scanf("%d", &sid);

		break;
	
	case 2:
		sid = atoi(argv[1]);

		break;

	default:
		fprintf(stderr, _("usage: -sename [sid]\n"));
		return 1;
	}

	session_name(sid, buf);
	printf("%d: %s\n", sid, buf);

	return 0;
}


int do_cmd_sid(int argc, char **argv) {
	char buf[USRNAME_SIZE];
	char *p;
	int sid;

	switch (argc) {
	case 1:
		fprintf(stderr, "session name: ");
		scanf("%s", buf);
		p = buf;

		break;

	case 2:
		p = argv[1];

		break;

	default:
		fprintf(stderr, _("usage: -sid [sname]\n"));

		break;
	}

	if ((sid = get_name_sid(p)) == -1) {
		fprintf(stderr, _("%s: No such session. \n"), p);

		return 1;
	}

	printf("%s: %d\n", p, sid);
	return 0;
}


int do_cmd_join(int argc, char **argv) {
	char buf[USRNAME_SIZE];
	char *p;

	switch (argc) {
	case 1:
		fprintf(stderr, "session name: ");
		scanf("%s", buf);
		p = buf;

		break;

	case 2:
		p = argv[1];
		break;

	default:
		fprintf(stderr, _("usage: -join [sname]\n"));

		break;
	}

	if ((current_sid = get_name_sid(p)) == -1) {
		fprintf(stderr, "%s: No such session. \n", p);
		current_sid = 0;

		return 1;
	}

	join_session(current_sid);

	return 0;
}

